using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Rosetta.Default
{
    [DebuggerDisplay("Method {Name}")]
    internal sealed class MethodEntry : IMethodEntry
    {
        #region Fields

        [CanBeNull]
        private string[] argumentNames;

        [CanBeNull]
        private Type[] _ArgumentTypes;

        [CanBeNull]
        private Type _ReturnType;

        [CanBeNull]
        private Type[] _ReturnTypes;

        #endregion

        #region Constructors

        public MethodEntry([NotNull] string name, [NotNull] Delegate method, string[] argumentNames)
        {
            Name = name;

            Method = method;
            if (argumentNames != null && argumentNames.Length > 0)
            {
                this.argumentNames = argumentNames;
            }

            var type = method.GetType();
            if (type.IsGenericType)
            {
                var genericType = type.GetGenericTypeDefinition();
                var genericArguments = type.GetGenericArguments();

                // Get Argument Types
                if (AddIn.IsConverterFuncType(genericType))
                {
                    _ArgumentTypes = genericArguments.Take(genericArguments.Length - 1).ToArray();
                }
                else if (AddIn.IsActionType(genericType))
                {
                    _ArgumentTypes = genericArguments;
                }

                if (this.argumentNames != null && _ArgumentTypes != null && this.argumentNames.Length != _ArgumentTypes.Length)
                {
                    throw new ArgumentException(string.Format("Incorrect number of argument names provided.\nGot {0} arguments and {1} names.", _ArgumentTypes.Length, this.argumentNames.Length), "argumentNames");
                }

                // Get Return Type
                if (genericType == typeof(Func<>) || AddIn.IsConverterFuncType(genericType))
                {
                    var returnType = genericArguments.Last();
                    var returnTypes = new List<Type> { returnType };

                    // Find IEnumerable<> implementations
                    AddReturnType(returnTypes, returnType);
                    foreach (Type interfaceType in returnType.GetInterfaces())
                    {
                        AddReturnType(returnTypes, interfaceType);
                    }

                    _ReturnType = returnType;
                    _ReturnTypes = returnTypes.ToArray();
                }
            }
        }

        #endregion

        #region Properties

        [NotNull]
        public Type[] ArgumentTypes
        {
            get
            {
                if (_ArgumentTypes == null)
                {
                    throw new InvalidOperationException("MethodEntry has no arguments.");
                }
                return _ArgumentTypes;
            }
        }

        public bool HasArgumentTypes
        {
            get
            {
                return _ArgumentTypes != null;
            }
        }

        public bool HasReturnTypes
        {
            get
            {
                return _ReturnTypes != null;
            }
        }

        [NotNull]
        public string Name { get; private set; }

        [NotNull]
        internal Delegate Method { get; private set; }

        [NotNull]
        public Type ReturnType
        {
            get
            {
                if (_ReturnType == null)
                {
                    throw new InvalidOperationException("MethodEntry has no return type.");
                }
                return _ReturnType;
            }
        }

        [NotNull]
        public Type[] ReturnTypes
        {
            get
            {
                if (_ReturnTypes == null)
                {
                    throw new InvalidOperationException("MethodEntry has no return type.");
                }
                return _ReturnTypes;
            }
        }

        #endregion

        #region Public Methods

        public string GetArgumentName(int argumentIndex)
        {
            if (_ArgumentTypes == null)
            {
                throw new InvalidOperationException("MethodEntry has no arguments.");
            }

            if (argumentNames == null)
            {
                // Validate index

                if (argumentIndex < 0 || argumentIndex >= _ArgumentTypes.Length)
                {
                    throw new ArgumentOutOfRangeException("argumentIndex", argumentIndex, string.Format("Value should be between 0 and {0}.", _ArgumentTypes.Length));
                }

                //return "arg" + argumentIndex;
                return null;
            }

            return argumentNames[argumentIndex];

        }

        [NotNull]
        public Type GetArgumentType(int argumentIndex)
        {
            return ArgumentTypes[argumentIndex];
        }

        #endregion

        #region Class Methods

        private static void AddReturnType(ICollection<Type> returnTypes, Type type)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            {
                var itemType = type.GetGenericArguments()[0];
                if (!returnTypes.Contains(itemType))
                {
                    returnTypes.Add(itemType);
                }
            }
        }

        #endregion
    }
}